############### This code replicates analysis in "Negative Sentiment and Congressional Cue-taking on Social Media" ###############


##### Read in packages 
library(plm)
library(lmtest)
library(multiwayvcov)
library(plyr)
library(stringr)
library(base64enc)
library(stargazer)
library(lubridate)
library(readr)
library(dplyr)

##### Set wd to wherever you have code and data 
setwd("") 


############### Read in data from 116th Congress ############### 

## Post-level dataframe 
cong16 <- read.csv("Cand_Fb_Posts_Replication_Cong16_Multimedia.csv", stringsAsFactors = FALSE)

## Dataframe aggregated by candidate-week-multimedia 
posts_df2 <- read.csv("Cand_Fb_Posts_Cleaned_Cong16_AGG_CandWeek_Multimedia.csv", stringsAsFactors = FALSE)

## Dataframe aggregated by candidate-week 
posts_week2 <- read.csv("Cand_Fb_Posts_Cleaned_Cong16_AGG_CandWeek_Overall.csv", stringsAsFactors = FALSE)


############### Analysis ############### 
options(digits = 3)


##### Components of Table 1: Facebook post frequency by candidate type 

cong16_cand_desc <- dplyr::select(cong16, fb_user, female, party, challenger, competitive)
cong16_cand_desc <- cong16_cand_desc %>% group_by(fb_user) %>% mutate(total_posts = n())
cong16_cand_desc <- unique(cong16_cand_desc)


### Number of each type of candidate in data (out of 696 total)
table(cong16_cand_desc$party) # 349 D, 347 R
table(cong16_cand_desc$female) # 244 ladies, 452 men
table(cong16_cand_desc$challenger) # 347 incumbent, 349 challenger
table(cong16_cand_desc$competitive) # 161 competitive, 535 not competitive 

### Overall summary stats
summary(cong16_cand_desc$total_posts)

### Gender 
women <- filter(cong16_cand_desc, female == 1)
summary(women$total_posts)

men <- filter(cong16_cand_desc, female == 0)
summary(men$total_posts)

### Party
dem <- filter(cong16_cand_desc, party == "Democratic")
summary(dem$total_posts)

gop <- filter(cong16_cand_desc, party == "Republican")
summary(gop$total_posts)

### Incumbency
inc <- filter(cong16_cand_desc, challenger == "incumbent")
summary(inc$total_posts)

chall <- filter(cong16_cand_desc, challenger == "challenger")
summary(chall$total_posts)

### District Competitiveness 
comp <- filter(cong16_cand_desc, competitive == 1)
summary(comp$total_posts)

notcomp <- filter(cong16_cand_desc, competitive == 0)
summary(notcomp$total_posts)


####### Regression Analysis 

##### Define OLS Function

ols_cand_wk_controls <- function(x, IV, data) {
  lm(log(x+1) ~ IV + female + party + challenger + 
       competitive + white + log(num_posts+1) + log(avg_likes+1) + multimedia + state + week, 
     data = data) 
} 


##### Models

### H1:
C2 <- ols_cand_wk_controls(x = posts_df2$avg_FBlikes, IV = posts_df2$prop_neg, data = posts_df2)
C4 <- ols_cand_wk_controls(x = posts_df2$avg_comment, IV = posts_df2$prop_neg, data = posts_df2)
C6 <- ols_cand_wk_controls(x = posts_df2$avg_shares, IV = posts_df2$prop_neg, data = posts_df2)


### H2:
C8 <- ols_cand_wk_controls(x = posts_df2$avg_FBsad, IV = posts_df2$prop_neg, data = posts_df2)
C10 <- ols_cand_wk_controls(x = posts_df2$avg_FBangry, IV = posts_df2$prop_neg, data = posts_df2)


### Table 2
stargazer(C2, C4, C6, C8, C10,
          title="OLS regressions of proportion of average negative words per post on candidates' logged average post likes, comments, shares, sad, and angry reactions received (by candidate-post type-week), with controls",
          dep.var.labels = "",
          column.labels = c("Avg. Post Likes",  
                            "Avg. Comments", "Avg. Shares", "Avg. Sad Reactions",  
                            "Avg. Angry Reactions"), 
          covariate.labels=c("Proportion Negative", "Female", "Republican", "Incumbent",
                             "Competitive", "White",
                             "Overall N Posts", 
                             "Overall Avg. Page Likes", "Multimedia"),
          omit = c("state", "week"), 
          notes.align= "r",
          font.size = "small", 
          omit.stat = c("ser", "adj.rsq"), 
          notes = "Models include week and state fixed effects.", 
          column.sep.width = "2pt")
